package topic

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"mall/common/cachex"
	"mall/common/ctxdatax"
	"mall/common/globalkey"
	"mall/common/mqueuex"
	"mall/common/resultx"
	"mall/service/backend/common/utils/pointy"
	"mall/service/forum/api/internal/lib/forumreaction"
	"mall/service/forum/api/internal/lib/userrank"
	"mall/service/forum/api/internal/logic/util"
	"mall/service/forum/api/internal/svc"
	"mall/service/forum/api/internal/types"
	"mall/service/forum/api/internal/utils/parse"
	"mall/service/forum/rpc/forumclient"
	"mall/service/user/rpc/userclient"

	"github.com/sirupsen/logrus"
	"github.com/zeromicro/go-zero/core/logx"
)

type GetReplyListLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewGetReplyListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetReplyListLogic {
	return &GetReplyListLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *GetReplyListLogic) GetReplyList(req *types.ReplyListReq) (*types.ReplyListInfo, error) {
	userId := ctxdatax.GetUserId(l.ctx)
	if userId == 0 {
		return nil, resultx.NewErrCode(resultx.TOKEN_EXPIRE_ERROR)
	}

	page, pageSize := parse.ParsePageInfo(
		req.Current,
		req.PageSize,
		l.svcCtx.Config.Query.DefaultAmount,
		l.svcCtx.Config.Query.MaxAmount,
	)

	var status *int64
	if req.Status != nil && *req.Status > 0 {
		status = pointy.GetPointer(int64(*req.Status))
	}

	var filters []string
	var preload string = "forumPost:Id,AuthorId,AuthorName,AuthorAvatar,AuthorIp,AuthorLevel,Status,Sticky"
	var eagerLoad string = "forumPost:forumMedia"
	var private bool = true
	var skipLevel bool
	if req.TopicId != nil && *req.TopicId > 0 { // 指定问题
		private = false // 大家的回复
		eagerLoad += ",total"
		filters = append(filters, "TopicId,equalTo:" + strconv.FormatInt(int64(*req.TopicId), 10))
		if status != nil {
			filters = append(filters, "Status,equalTo:" + strconv.FormatInt(*status, 10))
		} else {
			filters = append(filters, "Status,equalTo:" + strconv.FormatInt(int64(globalkey.StatusEnabled), 10))
		}
	} else { // 我的回复
		skipLevel = true
		preload = "forumTopic:Id,AuthorId,Sticky,Title,CreatedAt,AuthorName,AuthorAvatar,AuthorIp,AuthorLevel" + preload
		eagerLoad += ",forumTopic,total"
		filters = append(filters, "AuthorId,equalTo:" + strconv.FormatInt(int64(userId), 10))
		if status != nil {
			filters = append(filters, "Status,equalTo:" + strconv.FormatInt(*status, 10))
		} else {
			filters = append(filters, "Status,notEqualTo:" + strconv.FormatInt(int64(globalkey.StatusDeleted), 10))
		}
	}
	preload += ",ContentHtml,CommentStatus,CommentCount,CreatedAt;forumMedia:Id,Uid,Url,Thumbnail,Order"

	var sorter *string
	if req.Sorter != nil {
		sorter =  pointy.GetPointer(strings.TrimSpace(*req.Sorter))
	}
	var platformId uint32 = globalkey.ThirdPartyPlatformWechatMini

	res, err := l.svcCtx.ForumRpc.GetReplyList(l.ctx, &forumclient.BaseListReq{
		Offset:     uint32((page-1)*pageSize),
		Limit:      uint32(pageSize),
		Preload:    pointy.GetPointer(preload),
		EagerLoad:  pointy.GetPointer(eagerLoad),
		Filter:     pointy.GetPointer(strings.Join(filters, ";")),
		Sorter:     sorter,
		VisitorId:  pointy.GetPointer(userId),
		PlatformId: pointy.GetPointer(platformId),
	})
	if err != nil {
		return nil, err
	}

	var hasNextPage bool
	var hasPreviousPage bool
	if res.HasNextPage != nil && *res.HasNextPage > 0 {
		hasNextPage = true
	}
	if res.HasPreviousPage != nil && *res.HasPreviousPage > 0 {
		hasPreviousPage = true
	}
	resp := &types.ReplyListInfo{
		BaseListInfo: types.BaseListInfo{
			Total:           res.Total,
			Current:         page,
			PageSize:        pageSize,
			HasNextPage:     hasNextPage,
			HasPreviousPage: hasPreviousPage,
		},
	}

	if len(res.List) > 0 {
		var userRankMap map[uint32]*types.UserRankInfo
		userRankMap = l.getUserRankMap(userId)
		var replyIds []string
		var topicIds []string
		for _, v := range res.List {
			if v.Id != nil && *v.Id > 0 {
				replyIds = append(replyIds, strconv.FormatInt(int64(*v.Id), 10))
			}
			if v.Topic != nil && v.Topic.Id != nil && *v.Topic.Id > 0 {
				topicIds = append(topicIds, strconv.FormatInt(int64(*v.Topic.Id), 10))
			}
		}
		topicLikeMap := l.getTopicLikeBatch(topicIds, strconv.FormatInt(int64(userId), 10))
		replyLikeMap := l.getPostLikeBatch(replyIds, strconv.FormatInt(int64(userId), 10))

		for _, v := range res.List {
			var topic *types.TopicInfo
			if v.Topic != nil {
				var photos []*types.MediaInfo
				if len(v.Topic.Media) > 0 {
					for _, m := range v.Topic.Media {
						var url string
						if m.Url != nil && len(*m.Url) > 0 {
							url = *m.Url
						}
						if len(url) == 0 && m.Uri != nil && len(*m.Uri) > 0 {
							url = *m.Uri
						}
						var thumbnail *bool
						if m.Thumbnail != nil {
							thumbnail = pointy.GetPointer(*m.Thumbnail > 0)
						}
						photos = append(photos, &types.MediaInfo{
							Uid:       m.Uid,
							Id:        m.Id,
							Url:       pointy.GetPointer(url),
							Thumbnail: thumbnail,
							AltText:   m.AltText,
							Details:   m.Meta,
							Order:     m.Order,
						})
					}
				}

				var author *types.AuthorInfo
				var levelName *string
				var levelUrl *string
				if userRankMap != nil && v.Topic.Author != nil && v.Topic.Author.Level != nil && *v.Topic.Author.Level > 0 {
					if userRank, ok := userRankMap[*v.Topic.Author.Level]; ok {
						levelName = userRank.Name
						levelUrl  = userRank.MediaUrl
					}
				}
				if v.Topic.Author != nil && v.Topic.Author.Name != nil {
					var anonymous *bool
					if v.Topic.Author.Anonymous != nil {
						anonymous = pointy.GetPointer(*v.Topic.Author.Anonymous > 0)
					}
					author = &types.AuthorInfo{
						Id:        v.Topic.Author.Id,
						Name:      v.Topic.Author.Name,
						Avatar:    v.Topic.Author.Avatar,
						Ip:        v.Topic.Author.Ip,
						Level:     v.Topic.Author.Level,
						Anonymous: anonymous,
						LevelName: levelName,
						LevelUrl:  levelUrl,
					}
				}

				var isLiked *bool
				var likeCount *uint64
				if topicLikeMap != nil {
					if likeStatus, ok := topicLikeMap[int64(*v.Topic.Id)]; ok {
						isLiked   = pointy.GetPointer(likeStatus.Status > 0)
						likeCount = pointy.GetPointer(uint64(likeStatus.Count))
					}
				}
				var createdAt *string
				if v.Topic.CreatedAt != nil && len(*v.Topic.CreatedAt) > 0 {
					createdAt = pointy.GetPointer(util.GetTimeFromNow(*v.Topic.CreatedAt))
				}

				topic = &types.TopicInfo{
					Id:        v.Topic.Id,
					Title:     v.Topic.Title,
					Slug:      v.Topic.Slug,
					IsLiked:   isLiked,
					LikeCount: likeCount,
					CreatedAt: createdAt,
					UpdatedAt: v.Topic.CreatedAt,
					Author:    author,
					Photos:    photos,
				}
			}

			var photos []*types.MediaInfo
			if len(v.Media) > 0 {
				for _, m := range v.Media {
					var url string
					if m.Url != nil && len(*m.Url) > 0 {
						url = *m.Url
					}
					if len(url) == 0 && m.Uri != nil && len(*m.Uri) > 0 {
						url = *m.Uri
					}
					var thumbnail *bool
					if m.Thumbnail != nil {
						thumbnail = pointy.GetPointer(*m.Thumbnail > 0)
					}
					photos = append(photos, &types.MediaInfo{
						Uid:       m.Uid,
						Id:        m.Id,
						Url:       pointy.GetPointer(url),
						Thumbnail: thumbnail,
						AltText:   m.AltText,
						Details:   m.Meta,
					})
				}
			}

			var isLiked *bool
			var likeCount *uint64
			if replyLikeMap != nil && v.Id != nil && *v.Id > 0 {
				if likeStatus, ok := replyLikeMap[int64(*v.Id)]; ok {
					isLiked = pointy.GetPointer(likeStatus.Status > 0)
					likeCount = pointy.GetPointer(uint64(likeStatus.Count))
				}
			}

			var statusText *string
			if v.Status != nil {
				statusText = pointy.GetPointer(util.GetStatusText(int32(*v.Status)))
			}
			var author *types.AuthorInfo
			if !skipLevel && userRankMap != nil {
				var levelName *string
				var levelUrl *string
				if userRankMap != nil && userRankMap != nil && v.Author != nil && v.Author.Level != nil && *v.Author.Level > 0 {
					if userRank, ok := userRankMap[*v.Author.Level]; ok {
						levelName = userRank.Name
						levelUrl  = userRank.MediaUrl
					}
				}
				if v.Author != nil && v.Author.Name != nil {
					var anonymous *bool
					if v.Author.Anonymous != nil {
						anonymous = pointy.GetPointer(*v.Author.Anonymous > 0)
					}
					author = &types.AuthorInfo{
						Id:        v.Author.Id,
						Name:      v.Author.Name,
						Avatar:    v.Author.Avatar,
						Ip:        v.Author.Ip,
						Level:     v.Author.Level,
						Anonymous: anonymous,
						LevelName: levelName,
						LevelUrl:  levelUrl,
					}
				}
			}

			var commentStatus *bool
			if v.CommentStatus != nil {
				commentStatus = pointy.GetPointer(*v.CommentStatus > 0)
			}
			var createdAt *string
			if v.CreatedAt != nil && len(*v.CreatedAt) > 0 {
				createdAt = pointy.GetPointer(util.GetTimeFromNow(*v.CreatedAt))
			}

			resp.List = append(resp.List, &types.ReplyInfo{
				Id:            v.Id,
				TopicId:       v.TopicId,
				ContentHtml:   v.ContentHtml,
				Status:        v.Status,
				StatusText:    statusText,
				IsLiked:       isLiked,
				LikeCount:     likeCount,
				CommentStatus: commentStatus,
				CommentCount:  v.CommentCount,
				CreatedAt:     createdAt,
				UpdatedAt:     v.CreatedAt,
				Author:        author,
				Topic:         topic,
				Photos:        photos,
			})
		}
	}

	if private && req.New != nil && *req.New {
		var userMeta []*userclient.UserMetaInfo
		userMeta = append(userMeta, &userclient.UserMetaInfo{
			Key:   pointy.GetPointer(mqueuex.UserStatisticForumAnswerNew), // "_answered_question_new_status"
			Value: pointy.GetPointer("0"),
		})
		l.svcCtx.UserRpc.SetUserMeta(l.ctx, &userclient.SetUserMetaReq{
			UserId:   pointy.GetPointer(userId),
			UserMeta: userMeta,
		})
	}

	return resp, nil
}

func (l *GetReplyListLogic) getUserRankMap(userId uint64) (map[uint32]*types.UserRankInfo) {
	userRank := userrank.NewUserRank(l.ctx, l.svcCtx)
	userRankList, err := userRank.List(userId)
	if err != nil {
		logrus.Info(fmt.Sprintf("GetReplyList getUserRankMap err: %+v", err))
		return nil
	}

	if len(userRankList) > 0 {
		resp := make(map[uint32]*types.UserRankInfo)
		for _, v := range userRankList {
			if v.Id != nil && *v.Id > 0 {
				resp[uint32(*v.Id)] = v
			}
		}

		return resp
	}

	return nil
}

func (l *GetReplyListLogic) getTopicLikeBatch(topicIds []string, userId string) map[int64]*forumreaction.ForumReactionStatus {
	forumReaction := forumreaction.NewForumReaction(l.ctx, l.svcCtx, cachex.ForumQuestionLike)
	likeMap, err := forumReaction.GetReactionMap(topicIds, userId)
	if err != nil {
		logrus.Info(fmt.Sprintf("ForumReaction questionLikeMap err: %+v ", err))
		return nil
	}

	// logrus.Info(fmt.Sprintf("ForumReaction questionLikeMap : %+v ", likeMap))
	var objectIds []string
	var extraIds []string
	for i, item := range likeMap {
		// logrus.Info(fmt.Sprintf("ForumReaction questionLikeMap item %d : %+v ", i, item))
		if item.Status == -1 {
			objectIds = append(objectIds, strconv.FormatInt(i, 10))
		}
		if item.Count == -1 {
			extraIds = append(extraIds, strconv.FormatInt(i, 10))
		}
	}
	if len(objectIds) > 0 || len(extraIds) > 0 {
		logMap, extraMap, logErr := l.getTopicLikeMap(objectIds, userId, extraIds)
		if logErr != nil {
			logrus.Info(fmt.Sprintf("ForumReaction getTopicLikeMap err: %+v ", logErr))
			return nil
		}

		// for i, item := range logMap {
		// 	logrus.Info(fmt.Sprintf("ForumReaction logMap item %d : %+v ", i, item))
		// }
		for i, item := range likeMap {
			if item.Status == -1 {
				if status, exist := logMap[i]; exist { // 数据库有记录
					item.Status = status
					forumReaction.InitStatus(
						strconv.FormatInt(i, 10),
						userId,
						strconv.FormatInt(int64(status), 10),
					)
				} else {
					item.Status = 0
					forumReaction.InitStatus(strconv.FormatInt(i, 10), userId, "-1")
				}
			}
			if item.Count == -1 {
				if count, exist := extraMap[i]; exist { // 数据库有记录
					item.Count = count
					forumReaction.InitCount(strconv.FormatInt(i, 10), strconv.FormatInt(count, 10))
				} else {
					item.Count = 0
					forumReaction.InitCount(strconv.FormatInt(i, 10), "0")
				}
			}
			likeMap[i] = item
		}

		// logrus.Info(fmt.Sprintf("ForumReaction logMap : %+v ", logMap))
	}

	return likeMap
}

func (l *GetReplyListLogic) getPostLikeBatch(postIds []string, userId string) map[int64]*forumreaction.ForumReactionStatus {
	forumReaction := forumreaction.NewForumReaction(l.ctx, l.svcCtx, cachex.ForumAnswerLike)
	likeMap, err := forumReaction.GetReactionMap(postIds, userId)
	if err != nil {
		logrus.Info(fmt.Sprintf("ForumReaction answerLikeMap err: %+v ", err))
		return nil
	}

	// logrus.Info(fmt.Sprintf("ForumReaction answerLikeMap : %+v ", likeMap))
	var objectIds []string
	var extraIds []string
	for i, item := range likeMap {
		// logrus.Info(fmt.Sprintf("ForumReaction answerLikeMap item %d : %+v ", i, item))
		if item.Status == -1 {
			objectIds = append(objectIds, strconv.FormatInt(i, 10))
		}
		if item.Count == -1 {
			extraIds = append(extraIds, strconv.FormatInt(i, 10))
		}
	}
	if len(objectIds) > 0 || len(extraIds) > 0 {
		logMap, extraMap, logErr := l.getPostLikeMap(objectIds, userId, extraIds)
		if logErr != nil {
			logrus.Info(fmt.Sprintf("ForumReaction getPostLikeMap err: %+v ", logErr))
			return nil
		}

		// for i, item := range logMap {
		// 	logrus.Info(fmt.Sprintf("ForumReaction logMap item %d : %+v ", i, item))
		// }
		for i, item := range likeMap {
			if item.Status == -1 {
				if status, exist := logMap[i]; exist { // 数据库有记录
					item.Status = status
					forumReaction.InitStatus(
						strconv.FormatInt(i, 10),
						userId,
						strconv.FormatInt(int64(status), 10),
					)
				} else {
					item.Status = 0
					forumReaction.InitStatus(strconv.FormatInt(i, 10), userId, "-1")
				}
			}
			if item.Count == -1 {
				if count, exist := extraMap[i]; exist { // 数据库有记录
					item.Count = count
					forumReaction.InitCount(strconv.FormatInt(i, 10), strconv.FormatInt(count, 10))
				} else {
					item.Count = 0
					forumReaction.InitCount(strconv.FormatInt(i, 10), "0")
				}
			}
			likeMap[i] = item
		}

		// logrus.Info(fmt.Sprintf("ForumReaction logMap : %+v ", logMap))
	}

	return likeMap
}

func (l *GetReplyListLogic) getTopicLikeMap(
	topicIds []string,
	userId string,
	extraIds []string,
) (resp map[int64]int32, extra map[int64]int64, err error) {
	resp = make(map[int64]int32)
	extra = make(map[int64]int64)

	if len(topicIds) == 0 && len(extraIds) == 0 {
		return
	}

	var filters []string
	if len(topicIds) > 0 {
		filters = append(filters, "TopicId,in:" + strings.Join(topicIds, ","))
		filters = append(filters, "UserId,equalTo:" + userId)
		filters = append(filters, "Type,equalTo:" + cachex.ForumQuestionLike)
	}
	var preFilters []string
	if len(extraIds) > 0 {
		preFilters = append(preFilters, "TopicId,in:" + strings.Join(extraIds, ","))
		preFilters = append(preFilters, "Type,equalTo:" + cachex.ForumQuestionLike)
	}
	res, err := l.svcCtx.ForumRpc.GetReactionLogList(l.ctx, &forumclient.BaseListReq{
		Preload:         pointy.GetPointer("forumReactionLog:TopicId,UserId,Status,Count"),
		Filter:          pointy.GetPointer(strings.Join(filters, ";")),
		PreFilterEntity: pointy.GetPointer(cachex.ForumQuestionLike),
		PreFilter:       pointy.GetPointer(strings.Join(preFilters, ";")),
	})
	if err != nil {
		return nil, nil, err
	}

	if len(res.List) > 0 {
		for _, v := range res.List {
			if v.TopicId == nil || v.Status == nil || *v.TopicId == 0 || *v.Status == 0 {
				resp[int64(*v.TopicId)] = int32(*v.Status)
			}
		}
	}
	if len(res.Extra) > 0 {
		for _, v := range res.Extra {
			if v.TopicId == nil || v.Count == nil || *v.TopicId == 0 || *v.Count == 0 {
				extra[int64(*v.TopicId)] = int64(*v.Count)
			}
		}
	}

	return
}

func (l *GetReplyListLogic) getPostLikeMap(
	postIds []string,
	userId string,
	extraIds []string,
) (resp map[int64]int32, extra map[int64]int64, err error) {
	resp = make(map[int64]int32)
	extra = make(map[int64]int64)

	if len(postIds) == 0 && len(extraIds) == 0 {
		return
	}

	var filters []string
	if len(postIds) > 0 {
		filters = append(filters, "PostId,in:" + strings.Join(postIds, ","))
		filters = append(filters, "UserId,equalTo:" + userId)
		filters = append(filters, "Type,equalTo:" + cachex.ForumAnswerLike)
	}
	var preFilters []string
	if len(extraIds) > 0 {
		preFilters = append(preFilters, "PostId,in:" + strings.Join(extraIds, ","))
		preFilters = append(preFilters, "Type,equalTo:" + cachex.ForumAnswerLike)
	}
	res, err := l.svcCtx.ForumRpc.GetReactionLogList(l.ctx, &forumclient.BaseListReq{
		Preload:         pointy.GetPointer("forumReactionLog:PostId,UserId,Status,Count"),
		Filter:          pointy.GetPointer(strings.Join(filters, ";")),
		PreFilterEntity: pointy.GetPointer(cachex.ForumAnswerLike),
		PreFilter:       pointy.GetPointer(strings.Join(preFilters, ";")),
	})
	if err != nil {
		return nil, nil, err
	}

	if len(res.List) > 0 {
		for _, v := range res.List {
			if v.PostId == nil || v.Status == nil || *v.PostId == 0 || *v.Status == 0 {
				resp[int64(*v.PostId)] = int32(*v.Status)
			}
		}
	}
	if len(res.Extra) > 0 {
		for _, v := range res.Extra {
			if v.PostId == nil || v.Count == nil || *v.PostId == 0 || *v.Count == 0 {
				extra[int64(*v.PostId)] = int64(*v.Count)
			}
		}
	}

	return
}
